﻿using System;
using System.Globalization;

namespace Aeon.Emulator.DebugSupport
{
    /// <summary>
    /// Represents an address qualified by mode.
    /// </summary>
    [CLSCompliant(false)]
    public struct QualifiedAddress : IEquatable<QualifiedAddress>
    {
        #region Private Fields
        private AddressType type;
        private ushort segment;
        private uint offset;
        #endregion

        #region Constructors
        /// <summary>
        /// Initializes a new instance of the QualifiedAddress struct.
        /// </summary>
        /// <param name="addressType">Mode of address qualification.</param>
        /// <param name="segment">Segment or selector of the address.</param>
        /// <param name="offset">Offset of the address.</param>
        public QualifiedAddress(AddressType addressType, ushort segment, uint offset)
        {
            this.type = addressType;
            this.segment = segment;
            this.offset = offset;
        }
        /// <summary>
        /// Initializes a new instance of the QualifiedAddress struct.
        /// </summary>
        /// <param name="addressType">Mode of address qualification.</param>
        /// <param name="offset">Offset of the address.</param>
        public QualifiedAddress(AddressType addressType, uint offset)
        {
            this.type = addressType;
            this.offset = offset;
            this.segment = 0;
        }
        #endregion

        #region Operators
        /// <summary>
        /// Tests whether two addresses are equal.
        /// </summary>
        /// <param name="valueA">First address.</param>
        /// <param name="valueB">Second address.</param>
        /// <returns>Value indicating whether addresses are equal.</returns>
        public static bool operator ==(QualifiedAddress valueA, QualifiedAddress valueB)
        {
            return valueA.Equals(valueB);
        }
        /// <summary>
        /// Tests whether two addresses are not equal.
        /// </summary>
        /// <param name="valueA">First address.</param>
        /// <param name="valueB">Second address.</param>
        /// <returns>Value indicating whether addresses are not equal.</returns>
        public static bool operator !=(QualifiedAddress valueA, QualifiedAddress valueB)
        {
            return !valueA.Equals(valueB);
        }
        /// <summary>
        /// Adds a value to the address offset and returns the result.
        /// </summary>
        /// <param name="valueA">Address containing the base offset.</param>
        /// <param name="valueB">Value to add to the offset.</param>
        /// <returns>Resulting address.</returns>
        public static QualifiedAddress operator +(QualifiedAddress valueA, int valueB)
        {
            valueA.offset = (uint)(valueA.offset + valueB);
            return valueA;
        }
        #endregion

        #region Public Properties
        /// <summary>
        /// Gets the addressing mode.
        /// </summary>
        public AddressType AddressType
        {
            get { return this.type; }
        }
        /// <summary>
        /// Gets the segment or selector of the address. Null indicates a linear address.
        /// </summary>
        public ushort? Segment
        {
            get
            {
                if(this.type == AddressType.RealMode || this.type == AddressType.ProtectedMode)
                    return this.segment;
                else
                    return null;
            }
        }
        /// <summary>
        /// Gets the offset of the address. For linear addresses, this specifies the entire address.
        /// </summary>
        public uint Offset
        {
            get { return this.offset; }
        }
        #endregion

        #region Public Static Methods
        /// <summary>
        /// Returns a QualifiedAddress instance representing a physical address.
        /// </summary>
        /// <param name="physicalAddress">Physial memory address.</param>
        /// <returns>QualifiedAddress instance representing a physical address.</returns>
        public static QualifiedAddress FromPhysicalAddress(uint physicalAddress)
        {
            return new QualifiedAddress(AddressType.PhysicalLinear, physicalAddress);
        }
        /// <summary>
        /// Returns a QualifiedAddress instance respresenting a logical address.
        /// </summary>
        /// <param name="logicalAddress">Logical memory address.</param>
        /// <returns>QualifiedAddress instance representing a logical address.</returns>
        public static QualifiedAddress FromLogicalAddress(uint logicalAddress)
        {
            return new QualifiedAddress(AddressType.LogicalLinear, logicalAddress);
        }
        /// <summary>
        /// Returns a QualifiedAddress instance representing a real-mode memory address.
        /// </summary>
        /// <param name="segment">Real-mode address segment.</param>
        /// <param name="offset">Real-mode address offset.</param>
        /// <returns>QualifiedAddress representing a real-mode memory address.</returns>
        public static QualifiedAddress FromRealModeAddress(ushort segment, ushort offset)
        {
            return new QualifiedAddress(AddressType.RealMode, segment, offset);
        }
        /// <summary>
        /// Returns a QualifiedAddress instance representing a protected-mode memory address.
        /// </summary>
        /// <param name="selector">Protected-mode segment selector.</param>
        /// <param name="offset">Protected-mode segment offset.</param>
        /// <returns>QualifiedAddress representing a protected-mode memory address.</returns>
        public static QualifiedAddress FromProtectedModeAddress(ushort selector, uint offset)
        {
            return new QualifiedAddress(AddressType.ProtectedMode, selector, offset);
        }
        /// <summary>
        /// Parses an address string into a QualifiedAddress instance.
        /// </summary>
        /// <param name="s">Address string to parse.</param>
        /// <returns>QualifiedAddress instance if parsing was successful; otherwise null.</returns>
        public static QualifiedAddress? TryParse(string s)
        {
            if(s == null)
                throw new ArgumentNullException("s");

            s = s.Trim();
            if(s == string.Empty)
                return null;

            string[] parts;

            if(s[0] == '@')
            {
                s = s.Substring(1);
                parts = s.Split(':');
                if(parts.Length == 1)
                {
                    uint offset;
                    if(uint.TryParse(s, NumberStyles.HexNumber, CultureInfo.InvariantCulture.NumberFormat, out offset))
                        return FromPhysicalAddress(offset);

                    return null;
                }
                else if(parts.Length == 2)
                {
                    uint segment;
                    if(!uint.TryParse(parts[0], NumberStyles.HexNumber, CultureInfo.InvariantCulture.NumberFormat, out segment) || segment > ushort.MaxValue)
                        return null;

                    uint offset;
                    if(!uint.TryParse(parts[1], NumberStyles.HexNumber, CultureInfo.InvariantCulture.NumberFormat, out offset) || offset > ushort.MaxValue)
                        return null;

                    return FromRealModeAddress((ushort)segment, (ushort)offset);
                }

                return null;
            }

            parts = s.Split(':');
            if(parts.Length == 1)
            {
                uint offset;
                if(uint.TryParse(s, NumberStyles.HexNumber, CultureInfo.InvariantCulture.NumberFormat, out offset))
                    return FromLogicalAddress(offset);

                return null;
            }
            else if(parts.Length == 2)
            {
                uint segment;
                if(!uint.TryParse(parts[0], NumberStyles.HexNumber, CultureInfo.InvariantCulture.NumberFormat, out segment) || segment > ushort.MaxValue)
                    return null;

                uint offset;
                if(!uint.TryParse(parts[1], NumberStyles.HexNumber, CultureInfo.InvariantCulture.NumberFormat, out offset))
                    return null;

                return FromProtectedModeAddress((ushort)segment, offset);
            }

            return null;
        }
        #endregion

        #region Public Methods
        /// <summary>
        /// Returns a string representation of the QualifiedAddress instance.
        /// </summary>
        /// <returns>String representation of the QualifiedAddress.</returns>
        public override string ToString()
        {
            switch(this.type)
            {
            case AddressType.LogicalLinear:
                return this.Offset.ToString("X8");

            case AddressType.PhysicalLinear:
                return "@" + this.Offset.ToString("X8");

            case AddressType.RealMode:
                return string.Format("@{0:X4}:{1:X4}", this.segment, this.offset);

            case AddressType.ProtectedMode:
                return string.Format("{0:X4}:{1:X8}", this.segment, this.offset);

            default:
                return string.Empty;
            }
        }
        /// <summary>
        /// Tests for equality with another QualifiedAddress instance.
        /// </summary>
        /// <param name="other">Other QualifiedAddress instance to test for equality.</param>
        /// <returns>True if addresses are equal; otherwise false.</returns>
        public bool Equals(QualifiedAddress other)
        {
            return this.type == other.type && this.Segment == other.Segment && this.Offset == other.Offset;
        }
        /// <summary>
        /// Tests for equality with another object.
        /// </summary>
        /// <param name="obj">Other object to test for equality.</param>
        /// <returns>True if objects are equal; otherwise false.</returns>
        public override bool Equals(object obj)
        {
            if(obj is QualifiedAddress)
                return Equals((QualifiedAddress)obj);
            else
                return false;
        }
        /// <summary>
        /// Returns a hash code for the address.
        /// </summary>
        /// <returns>Hash code for the address.</returns>
        public override int GetHashCode()
        {
            return this.offset.GetHashCode();
        }
        #endregion
    }

    /// <summary>
    /// Describes the type of a qualified address.
    /// </summary>
    public enum AddressType : byte
    {
        /// <summary>
        /// The address is a logical 32-bit (paged) address.
        /// </summary>
        LogicalLinear,
        /// <summary>
        /// The address is a physical 32-bit (nonpaged) address.
        /// </summary>
        PhysicalLinear,
        /// <summary>
        /// The address is a real-mode segment:offset pair.
        /// </summary>
        RealMode,
        /// <summary>
        /// The address is a protected-mode selector:offset pair.
        /// </summary>
        ProtectedMode
    }
}
